using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Threading;
using Microsoft.BizTalk.ExplorerOM;
using Microsoft.BizTalk.Message.Interop;
using Microsoft.BizTalk.TransportProxy.Interop;

namespace Microsoft.Samples.BizTalk.Adapter.Tcp.Send
{
    /// <summary>
    ///     This worker thread is used to send data to the duplex listener in a receive adapter.
    /// </summary>
    /// <author>
    ///     Paul Brown
    /// </author>
    public class DuplexWorkerThread : WorkerThread
    {
        #region Private Instance Members

        private SendAdapter _sendAdapter;   // The send adapter

        #endregion

        #region Constructor

        /// <summary>
        ///     This is constructor initializes the instance.
        /// </summary>
        public DuplexWorkerThread(IAdapter adapter)
            : base(adapter)
        {
            // Get thread name prefix
            this.ThreadNamePrefix = TraceHelper.ResourceManager.GetString(ResourceConstants.SendDuplexWorkerThreadName);

            // Get reference to send adapter
            this._sendAdapter = (SendAdapter)adapter;
        }

        #endregion

        #region Public Instance Properties

        /// <summary>
        ///     This property returns a reference to the send adapter.
        /// </summary>
        public SendAdapter SendAdapter
        {
            get { return (this._sendAdapter); }
        }

        #endregion

        #region Private Instance Methods

        /// <summary>
        ///     This method creates a BizTalk message for submission into the batch.
        /// </summary>
        private IBaseMessage CreateMessage(Stream stream, IBaseMessageFactory messageFactory, DuplexWorkItem workItem)
        {
            // Create message part to contain the body of the message
            IBaseMessagePart part = messageFactory.CreateMessagePart();
            part.Data = stream;

            // Create message
            IBaseMessage message = messageFactory.CreateMessage();
            message.AddPart("body", part, true);

            // Return message
            return (message);
        }

        /// <summary>
        ///     This method retrieves the duplex listener address from the BizTalk Party.  If the BizTalk Party doesn't exist, it throws
        ///     a BizTalkPartyNotFoundException or if the address doesn't exist, it throws a DuplexAddressNotFoundException exception.
        /// </summary>
        private string GetDuplexAddress(string bizTalkPartyName)
        {
            // Get catalog object
            BtsCatalogExplorer catalog = new BtsCatalogExplorer();
            catalog.ConnectionString = SendAdapter.BizTalkManagementDatabaseConnectionString;

            // Get party
            Party bizTalkParty = catalog.Parties[bizTalkPartyName];

            if (bizTalkParty == null)
            {
                // Not found
                throw new BizTalkPartyNotFoundException(bizTalkPartyName);
            }

            // Find the duplex address
            string duplexAddress = string.Empty;
            foreach (PartyAlias alias in bizTalkParty.Aliases)
            {
                if (alias.Name == SendLocation.BizTalkPartyAliasName &&
                    alias.Qualifier == SendLocation.BizTalkPartyAliasQualifierDuplexAddress)
                {
                    duplexAddress = alias.Value;
                    break;
                }
            }

            if (duplexAddress == string.Empty)
            {
                // Not found
                throw new DuplexAddressNotFoundException(bizTalkPartyName);
            }

            // Return address found
            return (duplexAddress);
        }

        #endregion

        #region Abstract WorkerThread Members

        #region Public Instance Properties

        /// <summary>
        ///     This property returns the type of a BatchWorkItem which is compatible with this worker thread.
        /// </summary>
        public override Type WorkItemType
        {
            get { return (typeof(DuplexWorkItem)); }
        }

        #endregion

        #region Protected Instance Methods

        /// <summary>
        ///     This method is the delegate that runs the main worker thread.
        /// </summary>
        protected override void Thread()
        {
            // Variables
            bool            threadInterrupted = false;
            DuplexWorkItem  workItem = null;

            try
            {
                // Loop until we are interrupted
                while (true)
                {
                    // Loop while we have work items
                    while (this.CurrentWorkLoad > 0)
                    {
                        // Dequeue item
                        workItem = (DuplexWorkItem)this.DequeueWork();

                        if (this.Adapter.TraceSwitch.TraceVerbose)
                            TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendDequeuedDuplexWork, workItem.SendLocation.PartyEndpoint, this.Name);

                        try
                        {
                            // Resolve duplex address against BizTalk Party
                            string duplexUrl = this.GetDuplexAddress(workItem.SendLocation.PartyEndpoint);

                            // Parse the duplex URL and update send location endpoint address and port
                            Uri uri = new Uri(duplexUrl);
                            workItem.SendLocation.EndpointAddress = uri.Host;
                            workItem.SendLocation.EndpointPort = uri.Port;

                            if (this.Adapter.TraceSwitch.TraceVerbose)
                                TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendLocationAddressChanged, workItem.SendLocation.EndpointAddress, workItem.SendLocation.EndpointPort);

                            // Create a new TransmitWorkItem and dispatch
                            TransmitWorkItem transmitWorkItem = new TransmitWorkItem(workItem.SendLocation);
                            this.Adapter.Dispatcher.AddWorkItem(transmitWorkItem);

                            if (this.Adapter.TraceSwitch.TraceVerbose)
                                TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.SendDuplexTransmitWorkItemDispatched);
                        }
                        catch (ThreadInterruptedException)
                        {
                            // Asked to shut down, set flag and wait until this batch is done
                            threadInterrupted = true;
                        }
                        catch (Exception e)
                        {
                            // Create exception
                            string errorMessage = TraceHelper.FormatEntry(ResourceConstants.SendFailedResolvingDuplexAddress, this.Name, workItem.SendLocation.PartyEndpoint, e.Message);
                            TraceHelper.WriteRawEntry(TraceLevel.Error, errorMessage);
                            Exception msgEx = new Exception(errorMessage);

                            // Suspend message
                            BatchWorkItem batchWorkItem = new BatchWorkItem(workItem.SendLocation);
                            BatchMessage batchMessage = new BatchMessage(workItem.SendLocation.Message, msgEx);
                            batchMessage.BatchOperationType = BatchOperationType.MoveToSuspendQ;
                            batchWorkItem.BatchMessages.Add(batchMessage);

                            // Dispatch work
                            this.Adapter.Dispatcher.AddWorkItem(batchWorkItem);
                        }

                        // Update work item
                        workItem.Status = WorkItemStatusTypes.Complete;
                        workItem = null;
                    }

                    // Was the thread interrupted?
                    if (threadInterrupted)
                    {
                        // Throw error again to end thread
                        throw (new ThreadInterruptedException());
                    }

                    if (this.Adapter.TraceSwitch.TraceVerbose)
                        TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.WorkerThreadWaitingForWork, this.Name);

                    // Wait for signal that work has arrived
                    this.WaitForWork();
                }
            }
            catch (ThreadInterruptedException)
            {
                // Asked to shut down
                if (this.Adapter.TraceSwitch.TraceVerbose)
                    TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.WorkerThreadInterrupted, this.Name);
            }
            catch (Exception e)
            {
                TraceHelper.WriteEntry(TraceLevel.Error, ResourceConstants.WorkerThreadGeneralError, this.Name, e.Message);

                // If we have an outstanding work item, push it back onto queue before restarting
                if (workItem != null)
                {
                    this.PushWorkOnQueue(workItem);
                }
            }
            finally
            {
                // Fire terminate event
                this.OnThreadTerminated(new EventArgs());
            }
        }

        #endregion

        #endregion
    }
}
